/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.context.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.zhiqim.httpd.context.ZmlConfig;
import org.zhiqim.httpd.context.ZmlContext;
import org.zhiqim.httpd.context.ZmlContextConstants;
import org.zhiqim.kernel.constants.XmlConstants;
import org.zhiqim.kernel.model.maps.LinkedMapSV;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Lists;
import org.zhiqim.kernel.util.Resources;
import org.zhiqim.kernel.util.Strings;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.kernel.xml.Xmls;

/**
 * 上下文环境配置类
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class ZConfig implements ZmlContextConstants, XmlConstants
{
    private final ZmlConfig zmlConfig;
    private final boolean isRootConfig;
    
    //配置对应的资源类型和路径
    private final String resourceType;
    private final String resourcePath;
    
    //配置三项属性，名称、类型和路径
    private final String name;
    private final String path;
    
    //组件、配置、属性、拦截器、会话用户、功能
    private final List<ZComponent> componentList;
    private final List<ZConfig> configList;
    private final LinkedMapSV<ZAttribute> attributeMap;
    private final List<ZInterceptor> interceptorList;
    private final ZActionMap actionMap;
    
    /****************************************************************************************************/
    //构造函数&toString
    /****************************************************************************************************/
    
    /**
     * 构造函数六个参数
     * 
     * @param zmlConfig         ZML上下文件环境总配置类
     * @param isRootConfig      是否是上下文环境根配置
     * @param resourceType      上下文环境资源类型
     * @param resourcePath      上下文环境资源路径
     * @param name              当前配置的名称
     * @param path              当前配置的相对资源的路径
     */
    public ZConfig(ZmlConfig zmlConfig, boolean isRootConfig, String resourceType, String resourcePath, String name, String path)
    {
        this.zmlConfig = zmlConfig;
        this.isRootConfig = isRootConfig;
        
        this.resourcePath = Strings.removeEndsWith(resourcePath, "/");
        this.resourceType = resourceType;
        
        this.name = name;
        this.path = Strings.addStartsWith(path, "/");
        
        this.componentList = new ArrayList<>();
        this.configList = new ArrayList<>();
        this.attributeMap = new LinkedMapSV<>();
        this.interceptorList = new ArrayList<>();
        this.actionMap = new ZActionMap(zmlConfig, this);
    }
    
    public ZmlContext getContext()
    {
        return zmlConfig.getContext();
    }
    
    public String getName()
    {
        return name;
    }
    
    public String getPath()
    {
        return path;
    }
    
    public String toConfigString()
    {
        return new StringBuilder("<config")
            .append(" name=").append(_DOUBLE_QUOTE_).append(name).append(_DOUBLE_QUOTE_)
            .append(" path=").append(_DOUBLE_QUOTE_).append(path).append(_DOUBLE_QUOTE_)
            .append(" />")
            .toString();
    }
    
    public String toString()
    {
        StringBuilder strb = new StringBuilder();
        strb.append(_XML_DEFIND_).append(_BR_);
        strb.append(ZC_XML_DOCTYPE).append(_BR_);
        strb.append(ZC_XML_HEAD).append(_BR_).append(_BR_);
        
        //组件
        for (ZComponent component : componentList)
        {
            strb.append(_FOUR_).append(component.toString()).append(_BR_);
        }
        strb.append(_BR_);
        
        //嵌套上下文
        for (ZConfig context : configList)
        {
            strb.append(_FOUR_).append(context.toConfigString()).append(_BR_);
        }
        strb.append(_BR_);
        
        //属性
        for (ZAttribute attribute : attributeMap.values())
        {
            strb.append(_FOUR_).append(attribute.toString()).append(_BR_);
        }
        strb.append(_BR_);
        
        //拦截器
        for (ZInterceptor interceptor : interceptorList)
        {
            strb.append(_FOUR_).append(interceptor.toString()).append(_BR_);
        }
        strb.append(_BR_);
        
        //ACTION
        strb.append(actionMap.toString());
        strb.append(_BR_);
        
        strb.append(ZC_XML_TAIL);
        return strb.toString();
    }
    
    /****************************************************************************************************/
    //加载XML和组装配置表
    /****************************************************************************************************/
    
    /**
     * 加载XML
     * 
     * @throws Exception
     */
    public void load() throws Exception
    {
        InputStream in = getInputStream();
        Document document = null;
        
        try
        {
            document = Xmls.buildDocument(in, new ZCResolver(resourcePath + path));
        }
        catch(Exception e)
        {
            throw Asserts.exception("加载[%s%s]时异常", e, resourcePath, path);
        }
        
        //遍历<component>标签
        NodeList componentList = document.getElementsByTagName(ZC_COMPONENT);
        for (int i=0;i<componentList.getLength();i++)
        {
            Node node = componentList.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            
            String name = Xmls.getAttribute(node, ZC_NAME);
            String path = Xmls.getAttribute(node, ZC_PATH);
            
            addComponent(name, path);
        }
        
        //遍历<config>标签
        NodeList contextList = document.getElementsByTagName(ZC_CONFIG);
        for (int i=0;i<contextList.getLength();i++)
        {
            Node node = contextList.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            
            String name = Xmls.getAttribute(node, ZC_NAME);
            String path = Xmls.getAttribute(node, ZC_PATH);
            
            addConfig(name, path);
        }
        
        //遍历<attribute>标签
        NodeList attributeList = document.getElementsByTagName(ZC_ATTRIBUTE);
        for (int i=0;i<attributeList.getLength();i++)
        {
            Node node = attributeList.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            
            String name = Xmls.getAttribute(node, ZC_NAME);
            String key = Xmls.getAttribute(node, ZC_KEY);
            String value = Xmls.getAttribute(node, ZC_VALUE);
            
            addAttrute(name, key, value);
        }
        
        //遍历<interceptor>标签
        NodeList interceptorList = document.getElementsByTagName(ZC_INTERCEPTOR);
        for (int i=0;i<interceptorList.getLength();i++)
        {
            Node node = interceptorList.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            
            String name = Xmls.getAttribute(node, ZC_NAME);
            String key = Xmls.getAttribute(node, ZC_KEY);
            String clazz = Xmls.getAttribute(node, ZC_CLASS);
            
            addInterceptor(name, key, clazz);
        }
        
        //遍历<action>标签
        NodeList actionList = document.getElementsByTagName(ZC_ACTION);
        for (int i=0;i<actionList.getLength();i++)
        {
            Node node = actionList.item(i);
            if (node.getNodeType() != Node.ELEMENT_NODE)
                continue;
            
            NamedNodeMap namedNodeMap = node.getAttributes();
            String name = Xmls.getAttribute(namedNodeMap, ZC_NAME);
            String path = Xmls.getAttribute(namedNodeMap, ZC_PATH);
            
            String interceptor = Xmls.getAttribute(namedNodeMap, ZC_INTERCEPTOR);
            String forward = Xmls.getAttribute(namedNodeMap, ZC_FORWARD);
            String redirect = Xmls.getAttribute(namedNodeMap, ZC_REDIRECT);
            String view = Xmls.getAttribute(namedNodeMap, ZC_VIEW);
            String include = Xmls.getAttribute(namedNodeMap, ZC_INCLUDE);
            String clazz = Xmls.getAttribute(namedNodeMap, ZC_CLASS);
            String method = Xmls.getAttribute(namedNodeMap, ZC_METHOD);
            String success = Xmls.getAttribute(namedNodeMap, ZC_SUCCESS);
            String tips = Xmls.getAttribute(namedNodeMap, ZC_TIPS);
            
            addAction(name, path, interceptor, forward, redirect, view, include, clazz, method, success, tips);
        }
        
        in.close();
        
        //对嵌套上下文环境进行处理
        Lists.trim(this.configList);
        for (ZConfig conf : this.configList)
        {
            conf.load();
        }
    }
    
    private InputStream getInputStream() throws FileNotFoundException
    {
        String filePath = resourcePath + path;
        if (_CLASSPATH_.equals(resourceType))
        {
            if (!Resources.exists(ZConfig.class, filePath))
                throw new FileNotFoundException(filePath + " 不存在!");
            
            return Resources.getResourceStream(ZConfig.class, filePath);
        }
        else
        {
            File file = new File(filePath);
            if (!file.exists()) 
                throw new FileNotFoundException(filePath + " 不存在!");
         
            return new FileInputStream(file);
        }
    }
    
    private void addConfig(String name, String path)
    {
        Asserts.as(Validates.isNotEmptyBlank(path)?null:"上下文环境配置[%s], path不能为空", name);
        
        for (ZConfig config : configList)
        {//检查相同的配置路径
            Asserts.as((!config.getPath().equals(path))?null:"有相同的上下文环境配置[%s],path[%s]", name, path);
        }
        
        configList.add(new ZConfig(zmlConfig, isRootConfig, resourceType, resourcePath, name, path));
    }
    
    private void addComponent(String name, String path)
    {
        Asserts.as((Validates.isNotEmptyBlank(path)?null:"组件配置["+name+"], path不能为空"));
        
        ZComponent component = new ZComponent();
        component.setName(name);
        component.setPath(Strings.removeEndsWith(path, "/"));
        
        componentList.add(component);
    }
    
    private void addAttrute(String name, String key, String value)
    {
        Asserts.as(Validates.isNotEmptyBlank(key)?null:"属性配置[%s], key不能为空", name);
        
        ZAttribute attribute = new ZAttribute();
        attribute.setName(name);
        attribute.setKey(key);
        attribute.setValue(value);
        
        attributeMap.put(key, attribute);
    }
    
    private void addInterceptor(String name, String key, String clazz)
    {
        Asserts.as(Validates.isNotEmptyBlank(key)?null:"拦截器配置[%s], key不能为空", name);
        Asserts.as(Validates.isNotEmptyBlank(clazz)?null:"拦截器配置[%s], class不能为空", name);
        
        ZInterceptor interceptor = new ZInterceptor();
        interceptor.setName(name);
        interceptor.setKey(key);
        interceptor.setClazz(clazz);
        
        interceptorList.add(interceptor);
    }
    
    private void addAction(String name, String path, String interceptor, String forward, String redirect, String view, String include, 
        String clazz, String method, String success, String tips)
    {
        Asserts.as(Validates.isNotEmptyBlank(path)?null:"action配置[%s], path不能为空", name);
        
        ZAction action = new ZAction();
        action.setName(name);
        action.setPath(path);
        action.setInterceptor(interceptor);
        action.setForward(forward);
        action.setRedirect(redirect);
        action.setView(view);
        action.setInclude(include);
        action.setClazz(clazz);
        action.setMethod(method);
        action.setSuccess(success);
        action.setTips(tips);
        
        actionMap.addAction(action);
    }
    
    /****************************************************************************************************/
    //获取配置元素列表
    /****************************************************************************************************/
    
    public List<ZComponent> getComponentList()
    {
        return componentList;
    }
    
    public List<ZConfig> getConfigList()
    {
        return configList;
    }
    
    public Collection<ZAttribute> getAttributeList()
    {
        return attributeMap.values();
    }
    
    public List<ZInterceptor> getInterceptorList()
    {
        return interceptorList;
    }
    
    public Collection<ZAction> getExactActionList()
    {
        return actionMap.getExactActionList();
    }
    
    public Collection<ZAction> getFuzzyActionList()
    {
        return actionMap.getFuzzyActionList();
    }
    
    /** 检查变量和模拟排序 */
    public void chkActionVariableAndSort()
    {
        actionMap.chkVariableAndSort();
    }
    
    /*****************************************************************************/
    //通过path 获取ZAction
    /*****************************************************************************/
    
    public ZAction getInTurnAction(String path)
    {
        //1.先查本文件精确和模糊匹配
        ZAction action = actionMap.getExactAction(path);
        if (action != null)
            return action;
        
        action = actionMap.getFuzzyAction(path);
        if (action != null)
            return action;
        
        //2.再倒序查contextList中精确和模糊匹配
        for (int i=configList.size()-1;i>=0;i--)
        {
            //递归查到为止
            ZConfig context = configList.get(i);
            action = context.getInTurnAction(path);
            if (action != null)
                return action;
        }
        
        return null;
    }
    
    public ZAction getExactAction(String path)
    {
        //1.先查本文件精确匹配
        ZAction action = actionMap.getExactAction(path);
        if (action != null)
            return action;
        
        //2.再倒序查contextList中精确匹配
        for (int i=configList.size()-1;i>=0;i--)
        {
            //递归查到为止
            ZConfig context = configList.get(i);
            action = context.getExactAction(path);
            if (action != null)
                return action;
        }
        
        return null;
    }
    
    public ZAction getFuzzyAction(String path)
    {
        //1.先查本文件模糊匹配
        ZAction action = actionMap.getFuzzyAction(path);
        if (action != null)
            return action;
        
        //2.再倒序查contextList中模糊匹配
        for (int i=configList.size()-1;i>=0;i--)
        {
            //递归查到为止
            ZConfig context = configList.get(i);
            action = context.getFuzzyAction(path);
            if (action != null)
                return action;
        }
        
        return null;
    }
    
    /*****************************************************************************/
    //通过class 获取 ZAction
    /*****************************************************************************/
    
    public ZAction getInTurnActionByClass(String clazz)
    {
        //1.先查本文件精确和模糊匹配
        ZAction action = actionMap.getExactActionByClass(clazz);
        if (action != null)
            return action;
        
        action = actionMap.getFuzzyActionByClass(clazz);
        if (action != null)
            return action;
        
        //2.再倒序查contextList中精确和模糊匹配
        for (int i=configList.size()-1;i>=0;i--)
        {
            //递归查到为止
            ZConfig context = configList.get(i);
            action = context.getInTurnActionByClass(clazz);
            if (action != null)
                return action;
        }
        
        return null;
    }
    
    public ZAction getExactActionByClass(String clazz)
    {
        //1.先查本文件精确匹配
        ZAction action = actionMap.getExactActionByClass(clazz);
        if (action != null)
            return action;
        
        //2.再倒序查contextList中精确匹配
        for (int i=configList.size()-1;i>=0;i--)
        {
            //递归查到为止
            ZConfig context = configList.get(i);
            action = context.getExactActionByClass(clazz);
            if (action != null)
                return action;
        }
        
        return null;
    }
    
    public ZAction getFuzzyActionByClass(String clazz)
    {
        //1.先查本文件模糊匹配
        ZAction action = actionMap.getFuzzyActionByClass(clazz);
        if (action != null)
            return action;
        
        //2.再倒序查contextList中模糊匹配
        for (int i=configList.size()-1;i>=0;i--)
        {
            //递归查到为止
            ZConfig context = configList.get(i);
            action = context.getFuzzyActionByClass(clazz);
            if (action != null)
                return action;
        }
        
        return null;
    }
    
    /*****************************************************************************/
    //获取自身定义的三项配置（不能被覆盖）
    /*****************************************************************************/
    
    /** 获取引导 */
    public String getBootstrapClass()
    {
        ZAttribute attribute = attributeMap.get(ZC_BOOTSTRAP);
        if (attribute != null)
            return attribute.getValue();
        
        for (int i=configList.size()-1;i>=0;i--)
        {
            ZConfig context = configList.get(i);
            attribute = context.attributeMap.get(ZC_BOOTSTRAP);
            if (attribute != null)
                return attribute.getValue();
        }
        
        return null;
    }
    
    /** 获取包路径 */
    public ZActionPackage getActionPackage(ZmlConfig zmlConfig)
    {
        ZAttribute attribute = attributeMap.get(ZC_ACTION_PACKAGE);
        if (attribute != null)
            return new ZActionPackage(zmlConfig, this, attribute.getValue());
        
        for (int i=configList.size()-1;i>=0;i--)
        {
            ZConfig context = configList.get(i);
            attribute = context.attributeMap.get(ZC_ACTION_PACKAGE);
            if (attribute != null)
                return new ZActionPackage(zmlConfig, this, attribute.getValue());
        }
        
        return null;
    }
}
